Kotlin
Type-Safe Builders
Swift
|
A type-safe builder example
|
import com.example.html.* // see declarations below
fun result() =
html {
head {
title {+"XML encoding with Kotlin"}
}
body {
h1 {+"XML encoding with Kotlin"}
p {+"this format can be used as an alternative markup to XML"}
// an element with attributes and text content
a(href = "http://kotlinlang.org") {+"Kotlin"}
// mixed content
p {
+"This is some"
b {+"mixed"}
+"text. For more see the"
a(href = "http://kotlinlang.org") {+"Kotlin"}
+"project"
}
p {+"some text"}
// content generated by
p {
for (arg in args)
+arg
}
}
}
|
import com.example.html.* // see declarations below
func result() =
html {
head {
title {+"XML encoding with Kotlin"}
}
body {
h1 {+"XML encoding with Kotlin"}
p {+"this format can be used as an alternative markup to XML"}
// an element with attributes and text content
a(href = "http://kotlinlang.org") {+"Kotlin"}
// mixed content
p {
+"This is some"
b {+"mixed"}
+"text. For more see the"
a(href = "http://kotlinlang.org") {+"Kotlin"}
+"project"
}
p {+"some text"}
// content generated by
p {
for (arg in args)
+arg
}
}
}
|
How it works
|
html {
// ...
}
|
html {
// ...
}
|
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}
|
func html(init: HTML.() -> Unit): HTML {
let html = HTML()
html.init()
return html
}
|
html {
this.head { ... }
this.body { ... }
}
|
html {
this.head { ... }
this.body { ... }
}
|
html {
head { ... }
body { ... }
}
|
html {
head { ... }
body { ... }
}
|
fun head(init: Head.() -> Unit) : Head {
val head = Head()
head.init()
children.add(head)
return head
}
fun body(init: Body.() -> Unit) : Body {
val body = Body()
body.init()
children.add(body)
return body
}
|
func head(init: Head.() -> Unit) : Head {
let head = Head()
head.init()
children.add(head)
return head
}
func body(init: Body.() -> Unit) : Body {
let body = Body()
body.init()
children.add(body)
return body
}
|
protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
tag.init()
children.add(tag)
return tag
}
|
protected func <T : Element> initTag(tag: T, init: T.() -> Unit): T {
tag.init()
children.add(tag)
return tag
}
|
fun head(init: Head.() -> Unit) = initTag(Head(), init)
fun body(init: Body.() -> Unit) = initTag(Body(), init)
|
func head(init: Head.() -> Unit) = initTag(Head(), init)
func body(init: Body.() -> Unit) = initTag(Body(), init)
|
html {
head {
title {+"XML encoding with Kotlin"}
}
// ...
}
|
html {
head {
title {+"XML encoding with Kotlin"}
}
// ...
}
|
operator fun String.unaryPlus() {
children.add(TextElement(this))
}
|
operator func String.unaryPlus() {
children.add(TextElement(this))
}
|
Scope control: @DslMarker (since 1.1)
|
html {
head {
head {} // should be forbidden
}
// ...
}
|
html {
head {
head {} // should be forbidden
}
// ...
}
|
@DslMarker
annotation class HtmlTagMarker
|
@DslMarker
annotation class HtmlTagMarker
|
@HtmlTagMarker
abstract class Tag(val name: String) { ... }
|
@HtmlTagMarker
abstract class Tag(let name: String) { ... }
|
class HTML() : Tag("html") { ... }
class Head() : Tag("head") { ... }
|
class HTML() : Tag("html") { ... }
class Head() : Tag("head") { ... }
|
html {
head {
head { } // error: a member of outer receiver
}
// ...
}
|
html {
head {
head { } // error: a member of outer receiver
}
// ...
}
|
html {
head {
this@html.head { } // possible
}
// ...
}
|
html {
head {
this@html.head { } // possible
}
// ...
}
|
Full definition of the com.example.html package
|
package com.example.html
interface Element {
fun render(builder: StringBuilder, indent: String)
}
class TextElement(val text: String) : Element {
override fun render(builder: StringBuilder, indent: String) {
builder.append("$indent$text\n")
}
}
@DslMarker
annotation class HtmlTagMarker
@HtmlTagMarker
abstract class Tag(val name: String) : Element {
val children = arrayListOf<Element>()
val attributes = hashMapOf<String, String>()
protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
tag.init()
children.add(tag)
return tag
}
override fun render(builder: StringBuilder, indent: String) {
builder.append("$indent<$name${renderAttributes()}>\n")
for (c in children) {
c.render(builder, indent + " ")
}
builder.append("$indent</$name>\n")
}
private fun renderAttributes(): String {
val builder = StringBuilder()
for ((attr, value) in attributes) {
builder.append(" $attr=\"$value\"")
}
return builder.toString()
}
override fun toString(): String {
val builder = StringBuilder()
render(builder, "")
return builder.toString()
}
}
abstract class TagWithText(name: String) : Tag(name) {
operator fun String.unaryPlus() {
children.add(TextElement(this))
}
}
class HTML : TagWithText("html") {
fun head(init: Head.() -> Unit) = initTag(Head(), init)
fun body(init: Body.() -> Unit) = initTag(Body(), init)
}
class Head : TagWithText("head") {
fun title(init: Title.() -> Unit) = initTag(Title(), init)
}
class Title : TagWithText("title")
abstract class BodyTag(name: String) : TagWithText(name) {
fun b(init: B.() -> Unit) = initTag(B(), init)
fun p(init: P.() -> Unit) = initTag(P(), init)
fun h1(init: H1.() -> Unit) = initTag(H1(), init)
fun a(href: String, init: A.() -> Unit) {
val a = initTag(A(), init)
a.href = href
}
}
class Body : BodyTag("body")
class B : BodyTag("b")
class P : BodyTag("p")
class H1 : BodyTag("h1")
class A : BodyTag("a") {
var href: String
get() = attributes["href"]!!
set(value) {
attributes["href"] = value
}
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}
|
package com.example.html
interface Element {
func render(builder: StringBuilder, indent: String)
}
class TextElement(let text: String) : Element {
override func render(builder: StringBuilder, indent: String) {
builder.append("$indent$text\n")
}
}
@DslMarker
annotation class HtmlTagMarker
@HtmlTagMarker
abstract class Tag(let name: String) : Element {
let children = arrayListOf<Element>()
let attributes = hashMapOf<String, String>()
protected func <T : Element> initTag(tag: T, init: T.() -> Unit): T {
tag.init()
children.add(tag)
return tag
}
override func render(builder: StringBuilder, indent: String) {
builder.append("$indent<$name${renderAttributes()}>\n")
for (c in children) {
c.render(builder, indent + " ")
}
builder.append("$indent</$name>\n")
}
private func renderAttributes() -> String {
let builder = StringBuilder()
for ((attr, value) in attributes) {
builder.append(" $attr=\"$value\"")
}
return builder.toString()
}
override func toString() -> String {
let builder = StringBuilder()
render(builder, "")
return builder.toString()
}
}
abstract class TagWithText(name: String) : Tag(name) {
operator func String.unaryPlus() {
children.add(TextElement(this))
}
}
class HTML : TagWithText("html") {
func head(init: Head.() -> Unit) = initTag(Head(), init)
func body(init: Body.() -> Unit) = initTag(Body(), init)
}
class Head : TagWithText("head") {
func title(init: Title.() -> Unit) = initTag(Title(), init)
}
class Title : TagWithText("title")
abstract class BodyTag(name: String) : TagWithText(name) {
func b(init: B.() -> Unit) = initTag(B(), init)
func p(init: P.() -> Unit) = initTag(P(), init)
func h1(init: H1.() -> Unit) = initTag(H1(), init)
func a(href: String, init: A.() -> Unit) {
let a = initTag(A(), init)
a.href = href
}
}
class Body : BodyTag("body")
class B : BodyTag("b")
class P : BodyTag("p")
class H1 : BodyTag("h1")
class A : BodyTag("a") {
var href: String
get() = attributes["href"]!!
set(value) {
attributes["href"] = value
}
}
func html(init: HTML.() -> Unit): HTML {
let html = HTML()
html.init()
return html
}
|